#![cfg(target_os = "android")]

#[macro_use]
extern crate lazy_static;

use std::ffi::c_void;
use std::sync::Mutex;

use jni::{JavaVM, JNIEnv, NativeMethod};
use jni::objects::{JObject, JString, JValue};
use jni::strings::JNIString;
use jni::sys::*;

// 校验的包名
macro_rules! app_package { () => { "com.hulytu.android" }; }

// 检验的签名 hash-code 获取方式可使用 com.hulytu.android.hijack.Utils.getSignInfoHashCode 方式获取
macro_rules! signature { () => { -779219788 }; }

lazy_static! {
    static ref STATUS: Mutex<Status> =  Mutex::new(Status(false));
}

struct Status(bool);

#[inline]
fn is_valid_status() -> bool {
    STATUS.lock().unwrap().0
}

#[inline]
fn set_status(valid: bool) {
    STATUS.lock().unwrap().0 = valid
}

#[no_mangle]
#[allow(non_snake_case)]
fn JNI_OnLoad(jvm: JavaVM, _reserved: *mut c_void) -> jint {
    let env = jvm.get_env().unwrap();
    let version = env.get_version().unwrap();

    let methods = [
        NativeMethod { name: JNIString::from("init"), sig: JNIString::from("(Landroid/content/Context;)Z"), fn_ptr: init as *mut c_void },
        NativeMethod { name: JNIString::from("hijacked"), sig: JNIString::from("(Landroid/content/Context;)I"), fn_ptr: hijacked as *mut c_void },
        NativeMethod { name: JNIString::from("signature"), sig: JNIString::from("(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"), fn_ptr: signature as *mut c_void },
    ];

    // 注册native 方法
    let clazz = match env.find_class("com/hulytu/android/hijack/AntiHijack") {
        Ok(clazz) => clazz,
        Err(_) => return JNI_ERR,
    };

    let result = env.register_native_methods(clazz, &methods);

    return if result.is_ok() { version.into() } else { JNI_ERR };
}

fn init(env: JNIEnv, _: jclass, context: JObject) -> jboolean {
    if is_hook_pms(&env) || !is_valid_package(&env, &context) {
        set_status(false);
        return JNI_FALSE;
    }

    set_status(true);
    JNI_TRUE
}

fn hijacked(env: JNIEnv, _: jclass, context: JObject) -> jint {
    if is_hook_pms(&env) || !is_valid_package(&env, &context) {
        set_status(false);
        JNI_ERR
    } else {
        JNI_OK
    }
}

fn signature(env: JNIEnv, _: jclass, str: JString, ext: JString) -> jstring {
    if str.is_null() || !is_valid_status() {
        return env.new_string("").unwrap().into_inner();
    }

    let mut input: String = env
        .get_string(str)
        .expect("Couldn't get java string!")
        .into();

    let ext_str: String = env.get_string(ext)
        .expect("error")
        .into();

    input.push_str(&ext_str);

    let input = md5::compute(input.as_bytes());

    // Then we have to create a new java string to return. Again, more info
    // in the `strings` module.
    let output = env
        .new_string(format!("{:x}", input))
        .expect("Couldn't create java string!");

    // Finally, extract the raw pointer to return.
    output.into_inner()
}

/// 检测签名是否被hook
fn is_hook_pms(env: &JNIEnv) -> bool {
    let clazz = env.find_class("android/app/ActivityThread").unwrap();

    let manager = env.get_static_field(clazz, "sPackageManager", "Landroid/content/pm/IPackageManager;").unwrap();

    let proxy_clazz = env.find_class("java/lang/reflect/Proxy").unwrap();

    match env.is_instance_of(manager.l().unwrap(), proxy_clazz) {
        Ok(result) => result,
        Err(_) => true,
    }
}

fn is_valid_package(env: &JNIEnv, context: &JObject) -> bool {

    // #1
    let package_name = env.call_method(*context, "getPackageName", "()Ljava/lang/String;", &[]).unwrap();

    // 暂时没想到好的比较方案 临时解决办法
    let expect_package = env.new_string(app_package!())
        .expect("load string error.");

    let obj = JObject::from(expect_package);
    let equals = env.call_method(obj, "equals", "(Ljava/lang/Object;)Z", &[package_name]).unwrap();

    // 比较包名：如果包名不匹配，返回
    if matches!(equals, JValue::Bool(JNI_FALSE)) {
        return false;
    }

    // #2
    // 获取签名
    let manager = env.call_method(*context,
                                  "getPackageManager",
                                  "()Landroid/content/pm/PackageManager;",
                                  &[]).unwrap().l();

    let version_clazz = env.find_class("android/os/Build$VERSION").unwrap();
    let sdk_int = env.get_static_field(version_clazz,
                                       "SDK_INT",
                                       "I").unwrap().i().unwrap();


    let args = [package_name, JValue::Int(if sdk_int >= 28 { 0x08000000 } else { 64 })];
    let result = env.call_method(manager.unwrap(), "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;", &args);

    let pkg_info = match result {
        Ok(info) => info,
        Err(_) => return false,
    };

    let signatures: JValue;

    if sdk_int >= 28 {
        let result = env.get_field(pkg_info.l().unwrap(), "signingInfo", "Landroid/content/pm/SigningInfo;");
        let info = match result {
            Ok(sif) => sif,
            Err(_) => return false,
        };

        let result = env.call_method(info.l().unwrap(), "getApkContentsSigners", "()[Landroid/content/pm/Signature;", &[]);
        signatures = match result {
            Ok(rs) => rs,
            Err(_) => return false,
        };
    } else {
        // 低于 28 获取方式
        let result = env.get_field(pkg_info.l().unwrap(), "signatures", "[Landroid/content/pm/Signature;");

        signatures = match result {
            Ok(rs) => rs,
            Err(_) => return false,
        };
    }

    let signatures = signatures.l().unwrap();

    if signatures.is_null() {
        return false;
    }

    // #3
    let array = jobjectArray::from(*signatures);
    if env.get_array_length(array).unwrap() == 0 { return false; }
    let signature = env.get_object_array_element(array, jsize::from(0)).unwrap();

    if signature.is_null() { return false; }

    let hash = env.call_method(signature, "hashCode", "()I", &[]).unwrap().i().unwrap();

    hash == signature!()
}

// arm 32 和 64 位编译
// cargo ndk -t armeabi-v7a -t arm64-v8a -o ./jniLibs build --release

// cargo ndk -t armeabi-v7a  -o ./jniLibs build --release